package com.gyj.zookeeper.ZookeeperApi;

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * Created by Gao on 2018/10/23.
 * 核心类Zookeeper类，相关方法：
 * connect:连接到zookeeper集合
 * create:创建znode
 * exist：检查znode是否存在及其信息
 * getData：从特定的znode获取数据
 * setData:在特定的znode中设置数据
 * getChildren:获取特定的znode中的所有子节点
 * delete:删除特定的znode及其子项
 * close:关闭连接
 */
public class ZKApi {

    /**
     * ZKConnection
     * ZooKeeper类通过其构造函数提供connect power能。构造函数的签名如下：
     * ZooKeeper(String connectionString, int sessionTimeout, Watcher watcher)
     * connectionString - ZooKeeper集合主机。
     * sessionTimeout - 会话超时（以毫秒为单位）。
     * watcher  - 实现“监视器”界面的对象.ZooKeeper集合通过监视器对象返回连接状态。
     * 创建一个新的帮助类ZooKeeperConnection，并添加一个方法connect。 连接方法创建一个ZooKeeper对象，连接到ZooKeeper集合，然后返回对象。
     * 这里CountDownLatch用于停止（等待）主进程，直到客户端与动物园管理员集合连接。
     * ZooKeeper集合通过监视器回调来回复连接状态。一旦客户端与ZooKeeper集合连接，监视器回调就会被调用，
     * 并且监视器回调函数调用CountDownLatch的countDown方法来释放锁，在主进程中等待。
     */
    private ZooKeeper zooKeeper = null;
    private final CountDownLatch connectedSignal = new CountDownLatch(1);

    public ZooKeeper connect(String host) throws IOException, InterruptedException {
        zooKeeper = new ZooKeeper(host, 5000, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
                    connectedSignal.countDown();
                }
            }
        });
        connectedSignal.await();

        return zooKeeper;
    }

    public void close() throws InterruptedException {
        zooKeeper.close();
    }

    /**
     * create
     * create(String path, byte[] data, List<ACL> acl, CreateMode createMode)
     * path:Znode路径，例如，/ myapp1，/ myapp2，/ myapp1 / mydata1，myapp2 / mydata1 / myanothersubdata
     * data:要存储在指定znode路径中的数据
     * acl:要创建的节点的访问控制列表.ZooKeeper API提供了一个静态接口ZooDefs.Ids来获取一些基本的acl列表。
     * 例如，ZooDefs.Ids.OPEN_ACL_UNSAFE返回打开znode的acl列表。
     * createMode - 节点的类型，即临时，顺序或两者。这是一个枚举。
     */
    private static ZooKeeper zk;

    public static void create(String path, byte[] data) throws KeeperException, InterruptedException {
        zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    /**
     * exists
     * ZooKeeper类提供了存在方法来检查znode的存在 。如果指定的znode存在，则返回一个znode的元数据。存在方法的签名如下：
     * exists(String path, boolean watcher)
     * path - Znode路径
     * watcher - 布尔值，用于指定是否监视指定的znode
     */
    public static Stat exists(String path) throws KeeperException, InterruptedException {
        return zk.exists(path, true);
    }

    /**
     * getData
     * 动物园管理员类提供的getData方法来获取附加在指定Z序节点中的数据及其状态。 的getData方法的签名如下：
     * getData(String path, Watcher watcher, Stat stat)
     * path - Znode路径。
     * watcher  - 监视器类型的回调函数。当指定的znode的数据改变时，ZooKeeper集合将通过监视器回调进行通知。这是一次性通知。
     * stat - 返回znode的元数据。
     * <p>
     * setData(String path, byte[] data, int version)
     * path - Znode路径
     * data - 要存储在指定znode路径中的数据。
     * version - znode的当前版本。每当数据更改时，ZooKeeper会更新znode的版本号。
     * <p>
     * getChildren(String path, Watcher watcher)
     * path  - Znode路径。
     * watcher - 监视器类型的回调函数。当指定的znode被删除或znode下的子节点被创建/删除时，ZooKeeper集合将进行通知。这是一次性通知。
     * <p>
     * delete(String path, int version)
     * path  - Znode路径。
     * version  - znode的当前版本。
     */

    public static void main(String[] args) {

        String path = "/MyFirstZnode6";
        // data in byte array
        byte[] data = "My First zookeeper app".getBytes();
        try {
            ZKApi zkApi = new ZKApi();
            zk = zkApi.connect("localhost");
            //创建节点
            //zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            System.out.println("判断节点是否存在：");
            Stat stat = exists(path);
            if (stat != null) {
                //获取数据
                byte[] b = zk.getData(path, new Watcher() {
                    public void process(WatchedEvent we) {
                        if (we.getType() == Event.EventType.None) {
                            System.out.println("111111111111");
                            switch (we.getState()) {
                                case Expired:
                                    zkApi.connectedSignal.countDown();
                                    break;
                            }
                        } else {
                            System.out.println("2222222222");
                            try {
                                byte[] bn = zk.getData(path, false, null);
                                String data = new String(bn, "UTF-8");
                                System.out.println("节点数据：" + data);
                                zkApi.connectedSignal.countDown();
                            } catch (Exception ex) {
                                System.out.println(ex.getMessage());
                            }
                        }
                    }
                }, null);
                String data1 = new String(b, "UTF-8");
                System.out.println(data1);
                zkApi.connectedSignal.await();
            } else {
                System.out.println("Node does not exists");
            }

            //设置数据
            byte[] setData = "success".getBytes();
            zk.setData(path, setData, zk.exists(path, true).getVersion());
            //获取zk子节点
            List<String> children = zk.getChildren(path, false);
            for (int i = 0; i < children.size(); i++) {
                System.out.println("子节点：" + children.get(i)); //Print children's
            }
            //删除zk, -1可以匹配任何版本，也就删除了这个目录节点所有数据
            //zk.delete(path, zk.exists(path, true).getVersion());
            zk.delete(path, -1);
            //关闭zk
            zkApi.close();
        } catch (IOException | InterruptedException | KeeperException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }
}
